package com.dddframework.security.domain.phone.component;

import com.dddframework.security.domain.phone.model.PhoneAuthToken;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 手机号登录验证filter
 *
 * @author zhouzx
 */
@Getter
@Setter
public class PhoneAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private static final String SPRING_SECURITY_FORM_PHONE_KEY = "phone";
    private static final String SPRING_SECURITY_FORM_CODE_KEY = "code";
    private String phoneParameter = SPRING_SECURITY_FORM_PHONE_KEY;
    private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
    private boolean postOnly = true;
    private AuthenticationEventPublisher eventPublisher;
    private AuthenticationEntryPoint authenticationEntryPoint;

    public PhoneAuthenticationFilter() {
        super(new AntPathRequestMatcher("/phone/token/sms", "POST"));
    }

    @Override
    @SneakyThrows
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
        if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        String phone = obtainPhone(request);
        String code = obtainCode(request);
        if (phone == null) {
            phone = "";
        }
        phone = phone.trim();
        PhoneAuthToken phoneAuthToken = new PhoneAuthToken(phone, code);
        setDetails(request, phoneAuthToken);
        try {
            Authentication authentication = this.getAuthenticationManager().authenticate(phoneAuthToken);
            logger.debug("Authentication ok: " + authentication);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            return authentication;
        } catch (Exception failed) {
            SecurityContextHolder.clearContext();
            logger.debug("Authentication request failed: " + failed);
            eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
            try {
                authenticationEntryPoint.commence(request, response, new UsernameNotFoundException(failed.getMessage(), failed));
            } catch (Exception e) {
                logger.error("authenticationEntryPoint handle error:{}", failed);
            }
        }
        return null;
    }

    private String obtainPhone(HttpServletRequest request) {
        return request.getParameter(phoneParameter);
    }

    private String obtainCode(HttpServletRequest request) {
        return request.getParameter(codeParameter);
    }

    private void setDetails(HttpServletRequest request, PhoneAuthToken phoneAuthToken) {
        phoneAuthToken.setDetails(authenticationDetailsSource.buildDetails(request));
    }
}